package org.aplikator.server.rpc;

import org.aplikator.client.shared.data.*;
import org.aplikator.client.shared.descriptor.*;
import org.aplikator.client.shared.rpc.AplikatorService;
import org.aplikator.client.shared.rpc.ParameterWrapperGetPage;
import org.aplikator.client.shared.rpc.ParameterWrapperRunFunction;
import org.aplikator.server.AplikatorServiceServer;
import org.aplikator.server.DescriptorRegistry;
import org.aplikator.server.descriptor.*;
import org.aplikator.server.function.FunctionParameters;
import org.aplikator.server.function.FunctionResult;
import org.aplikator.server.impl.ContextImpl;
import org.aplikator.server.persistence.Persister;
import org.aplikator.server.persistence.PersisterFactory;
import org.aplikator.server.persistence.PersisterTriggers;
import org.aplikator.server.persistence.Transaction;
import org.aplikator.server.persistence.search.ReindexDescriptor;
import org.aplikator.server.persistence.search.SearchFactory;
import org.aplikator.server.processes.CannotCallStopException;
import org.aplikator.server.processes.Process;
import org.aplikator.server.processes.ProcessManager;
import org.aplikator.server.query.QueryExpression;
import org.jboss.errai.bus.server.annotations.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.core.Context;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * The server side implementation of the RPC service.
 */
@Service
public class AplikatorServiceImpl /*extends RemoteServiceServlet*/ implements AplikatorService, AplikatorServiceServer {
    private static final Logger LOG = Logger.getLogger(AplikatorServiceImpl.class.getName());



    private Persister persister = PersisterFactory.getPersister();
    @Context
    HttpServletRequest httpServletRequest;
    @Context
    HttpServletResponse httpServletResponse;
    
    
    
    
    private ContextImpl ctx(){
        return new ContextImpl(httpServletRequest, httpServletResponse, this);
    }


    @Override
    public ApplicationDTO getApplication(){
        return Application.get().getApplicationDTO(ctx());
    }

    @Override
    public ViewDTO getView(String id)  {
        View view = (View) DescriptorRegistry.get().getDescriptionItem(id);
        ViewDTO retVal = view.getViewDTO(ctx());
        return retVal;
    }
    
    private void processAfterCommit(RecordContainer rc, org.aplikator.server.Context ctx) {
        RecordContainer container = rc;
        for (int progressIter=0;progressIter<container.getRecords().size();progressIter++) {
        	ContainerNode node = container.getRecords().get(progressIter);
            PersisterTriggers trigger = null;
            
//            /ViewDTO viewDTO = node.getView();
            String viewId = node.getViewId();
            View view=null;
            if (viewId != null){
                view = (View) DescriptorRegistry.get().getDescriptionItem(viewId);
                trigger= view.getPersisterTriggers();
            }else{
                if (node.getOriginal()!= null){
                    Entity entity = (Entity) DescriptorRegistry.get().getDescriptionItem(node.getOriginal().getPrimaryKey().getEntityId());
                    trigger=entity.getPersisterTriggers();
                }else if (node.getEdited()!= null){
                    Entity entity = (Entity) DescriptorRegistry.get().getDescriptionItem(node.getEdited().getPrimaryKey().getEntityId());
                    trigger=entity.getPersisterTriggers();
                }
            }
                        
            switch (node.getOperation()) {
                case CREATE:
                    if (trigger != null) {
                        trigger.afterCommit(node.getEdited(), ctx);
                    }
                    break;
                case UPDATE:
                    if (trigger != null) {
                    	if ((view!=null)&&(view instanceof SubView)) {
                    		Property<String> discriminator = ((SubView) view).getDiscriminator();
                        	discriminator.setValue(node.getEdited(), discriminator.getValue(node.getOriginal()));                    
                        }
                        
                    	trigger.afterCommit(node.getEdited(), ctx);
                    }                    
                    break;
                case DELETE:
                    if (trigger != null) {
                        trigger.afterCommit(node.getOriginal(), ctx);
                    }
                    break;
            }
        }    	
    }

    @Override
    public RecordContainer processRecords(RecordContainer rc) {
        LOG.info("processing container");
        ContextImpl ctx = ctx();
        ctx.setRecordContainer(rc);
        RecordContainer container = rc;
        Transaction tx = null;
        ReindexDescriptor reindexDescriptor = new ReindexDescriptor();
        try {
            tx = persister.beginTransaction();
            for (int progressIter=0;progressIter<container.getRecords().size();progressIter++) {
            	ContainerNode node = container.getRecords().get(progressIter);
            	PersisterTriggers trigger = null;
            	String viewId = node.getViewId();
//            	ViewDTO viewDTO = node.getView();

                if (viewId != null){
                    View view = (View) DescriptorRegistry.get().getDescriptionItem(viewId);
                    trigger= view.getPersisterTriggers();
                }else{
                    if (node.getOriginal()!= null){
                        Entity entity = (Entity) DescriptorRegistry.get().getDescriptionItem(node.getOriginal().getPrimaryKey().getEntityId());
                        trigger=entity.getPersisterTriggers();
                    }else if (node.getEdited()!= null){
                        Entity entity = (Entity) DescriptorRegistry.get().getDescriptionItem(node.getEdited().getPrimaryKey().getEntityId());
                        trigger=entity.getPersisterTriggers();
                    }
                }
                
                Entity entity=null;
                switch (node.getOperation()) {
                    case CREATE:
                        
                        if (trigger != null) {
                            trigger.onCreate(node.getEdited(), ctx);
                        }
                
                        entity = (Entity) DescriptorRegistry.get().getDescriptionItem(node.getEdited().getPrimaryKey().getEntityId());                        
                        
                        node.setEdited(persister.updateRecord(node.getEdited().getPrimaryKey().getEntityId(), node.getEdited(), tx, ctx));

                        if (entity.isIndexed()) {
                        	reindexDescriptor.addDescriptor(node.getEdited().getPrimaryKey(), Operation.CREATE);
                        }
                        
                        if (trigger != null) {
                            trigger.onLoad(node.getEdited(), ctx);
                        }
                        break;
                    case UPDATE:
                        if (trigger != null) {
                            trigger.onUpdate(node.getEdited(), ctx);
                        }
                        persister.updateRecord(node.getEdited().getPrimaryKey().getEntityId(), node.getEdited(), tx, ctx);
                        
                        entity = (Entity) DescriptorRegistry.get().getDescriptionItem(node.getEdited().getPrimaryKey().getEntityId());                        
                        reindexDescriptor.addDescriptor(node.getEdited().getPrimaryKey(), Operation.UPDATE);
                        
                        if (trigger != null) {
                            trigger.onLoad(node.getEdited(), ctx);
                        }
                        break;
                    case DELETE:
                        if (trigger != null) {
                            trigger.onDelete(node.getOriginal(), ctx);
                        }
                        persister.deleteRecord(node.getOriginal().getPrimaryKey().getEntityId(), node.getOriginal().getPrimaryKey().getId(), tx, ctx);
                        
                        entity = (Entity) DescriptorRegistry.get().getDescriptionItem(node.getOriginal().getPrimaryKey().getEntityId());
                        
                        reindexDescriptor.addDescriptor(node.getOriginal().getPrimaryKey(), Operation.DELETE);
                        
                        break;
                }
            }
            if (SearchFactory.isSearchEnabled()) {
                reindexDescriptor.processReindex(tx);
            }
            persister.commitTransaction(tx);  
            processAfterCommit(rc, ctx);            
        } catch (Throwable th) {
            if (persister != null &&  tx!= null) persister.rollbackTransaction(tx);
            LOG.log(Level.SEVERE, "Error in processing records:", th);
            throw new RuntimeException("Error in processing records:", th);
        } finally {
            persister.close(tx);
        }

        for (ContainerNode cn : container.getRecords()){
            if (cn.getOperation().equals(Operation.CREATE)||cn.getOperation().equals(Operation.UPDATE)){
                PrimaryKey pk = cn.getEdited().getPrimaryKey();
                String viewId = cn.getViewId();
                Record updated = persister.getRecord(viewId, pk, ctx);
                if (cn.getOperation().equals(Operation.CREATE)){
                    updated.getPrimaryKey().setTempId(pk.getTempId());
                }
                cn.setOriginal(updated);
            }
        }
        return container;
    }

    public PropertyDTO getActivePrimarySortProperty(String activeSort, List<SortDescriptorDTO> sorts){
        if (activeSort!= null && !activeSort.equals("")){
            for(SortDescriptorDTO sd: sorts){
                if(sd.getId().equals(activeSort)){
                    return sd.getPrimarySortProperty();
                }
            }
            throw new IllegalStateException("SortDescriptor "+activeSort+" not found");
        } else if (sorts.size()>0){
            activeSort = sorts.get(0).getId();
            return sorts.get(0).getPrimarySortProperty();
        }
        return null;
    }

    
    @Override
    public SuggestionsDTO getSuggestions(String viewid, String req) {
    
    	ContextImpl context = ctx();
        //view.setSearchString(req);
        View view = (View) DescriptorRegistry.get().getDescriptionItem(viewid);
        SortDescriptor sortDescriptor = view.getSortDescriptor(req);
        Property p = sortDescriptor.getPrimarySortProperty();
        
        String activeFilter = null;
        String activeSort = null;
        List<QueryParameter> params = new ArrayList<QueryParameter>();
        String searchString = null;
        int offset = persister.getRecordCount(viewid,activeFilter,activeSort,params,searchString,  null, null, true, context);
        List<Record> records = persister.getPage(viewid,activeFilter,activeSort,params, null, null, offset, 20, context);

        List<ItemSuggestion> suggestions = new ArrayList<ItemSuggestion>(20);

        for (Record rec : records) {
            int pk = rec.getPrimaryKey().getId();
            Object val = p.getValue(rec);
            String repl = val!=null?val.toString():"";
            suggestions.add(new ItemSuggestion(rec.getPreview(), pk, repl, rec));
        }
        SuggestionsDTO sug = new SuggestionsDTO();
        sug.setSuggestions(suggestions);
        return sug;
    }

    @Override
    public Record getRecord(String primaryKey, String viewId){
        return persister.getRecord(viewId, new PrimaryKey(primaryKey), ctx());
    }

    @Override
    public RecordsPageDTO getPage(ParameterWrapperGetPage parameterWrapperGetPage, String ownerPrimaryKey, int pageOffset, int pageSize){
    	RecordsPageDTO page = new RecordsPageDTO();
    	page.setRecords(persister.getPage(parameterWrapperGetPage.getViewId(), null, null, new ArrayList<QueryParameter>(), parameterWrapperGetPage.getOwnerProperty(),(ownerPrimaryKey==null||ownerPrimaryKey.trim().length()==0)?null:new PrimaryKey(ownerPrimaryKey), pageOffset, pageSize, ctx()));   
    	return page;
    }

    @Override
    public SearchResult getPageSearch(String viewId, String searchArgument, int pageOffset, int pageSize){
    	return SearchFactory.get().getPagefromSearch(viewId,searchArgument, pageOffset, pageSize, ctx());        
    }
    
    @Override
    public int getRecordCount(ParameterWrapperGetPage parameterWrapperGetPage, String ownerPrimaryKey, boolean search) {
        return persister.getRecordCount(parameterWrapperGetPage.getViewId(),null,null, new ArrayList<QueryParameter>(), null, parameterWrapperGetPage.getOwnerProperty(), (ownerPrimaryKey==null||ownerPrimaryKey.trim().length()==0)?null:new PrimaryKey(ownerPrimaryKey), search, ctx());
    }

    @Override
    public String runFunction(ParameterWrapperRunFunction parameterWrapperRunFunction) {
        Function func = (Function) DescriptorRegistry.get().getDescriptionItem(parameterWrapperRunFunction.getFunction().getId());
        FunctionResult result = func.getExecutable().execute(new FunctionParameters(parameterWrapperRunFunction.getClientContext(), parameterWrapperRunFunction.getFunctionParameters()), ctx());
        return result != null ? result.getMessage() : "";
    }

    @Override
    public FunctionDTO getFunction(String id) {
        Function function = (Function) DescriptorRegistry.get().getDescriptionItem(id);
        return function.getFunctionDTO(ctx());
    }

    @Override
    public WizardPageDTO getWizardPage(String funcId, String curPage, boolean forwardFlag, ParameterWrapperRunFunction parameterWrapperRunFunction) {
        ContextImpl context = ctx();
        Function func = (Function) DescriptorRegistry.get().getDescriptionItem(funcId);
        Record record = parameterWrapperRunFunction.getClientContext().getCurrentRecord();
        Record clientRecordProperties = parameterWrapperRunFunction.getFunctionParameters();
        WizardPage wizPage = func.getExecutable().getWizardPage(curPage,forwardFlag, record, clientRecordProperties, context);
        return wizPage == null ? null : wizPage.getViewDTO(context);
    }



    @Override
    public Record getCompleteRecord( String primaryKey,  int traverseLevel, boolean includeCollections) {
        Record completeRecord = persister.getCompleteRecord(new PrimaryKey(primaryKey), traverseLevel, includeCollections,null);
        return completeRecord;
    }


    @Override
    public Record prepareRecord(String primaryKey, String viewId){
        ContextImpl ctx = ctx();
        View view = (View) DescriptorRegistry.get().getDescriptionItem(viewId);
        Record retval = null;
        if (primaryKey != null && !"".equals(primaryKey)){
            retval = persister.getRecord(viewId, new PrimaryKey(primaryKey), ctx).clone();
        } else{
            retval = new Record(new PrimaryKey(view.getEntity().getId(), UUID.uuid()));
        }
        PersisterTriggers trigger = view.getPersisterTriggers();;
        if (trigger != null) {
            trigger.onPrepare(retval, primaryKey == null, ctx);
        }
        return retval;
    }

    //@Override
    public List<Record> getRecords(View view, QueryExpression queryExpression, SortItem[] sortItems, PropertyDTO ownerProperty, PrimaryKey ownerPrimaryKey, int pageOffset, int pageSize, org.aplikator.server.Context ctx) {
        return persister.getRecords(view, queryExpression, sortItems, ownerProperty, ownerPrimaryKey, pageOffset, pageSize, ctx);
    }

    @Override
    public List<ProcessDTO> getProcesses() {
        ProcessManager pm = ProcessManager.getManager();
        List<Process> prs = pm.getProcesses();
        List<ProcessDTO>pdtos = new ArrayList<ProcessDTO>();
        for (Process ps : prs) {
            pdtos.add(ps.toProcessDTO());
        }
        return pdtos;
    }

    @Override
    public ProcessDTO getProcess(String procId) {
        ProcessManager pm = ProcessManager.getManager();
        Process p = pm.lookUpProcess(procId);
        return p != null ? p.toProcessDTO() : null;
    }

    @Override
    public ProcessDTO stopProcess(String procId, String stop) {
        ProcessManager pm = ProcessManager.getManager();
        Process p = pm.lookUpProcess(procId);
        if (p != null) {
            try {
                p.stopMe();
            } catch (CannotCallStopException ex) {
                Logger.getLogger(AplikatorServiceImpl.class.getName()).log(Level.SEVERE, null, ex);
            }
            return p.toProcessDTO();
        } else return null;
    }

    @Override
    public ProcessDTO deleteProcess(String procId) {
        ProcessManager pm = ProcessManager.getManager();
        Process p = pm.lookUpProcess(procId);
        pm.deregisterProcess(p);
        return p.toProcessDTO();
    }

}
